//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <stub.h>
#include <syscall.h>

#include "exception.h"

DECL_ASMLINKAGE void CDECL UpdateTime(InterruptContext *pContext);

void DumpCxt(InterruptContext *pCxt)
{
    uint32_t uCause = 0;

    kprintf("sp= %08x\tra= %08x\tv0= %08x\tv1= %08x\n",
        pCxt->regs[SP], pCxt->regs[RA], pCxt->regs[V0], pCxt->regs[V1]);
    kprintf("a0= %08x\ta1= %08x\ta2= %08x\ta3= %08x\n",
        pCxt->regs[A0], pCxt->regs[A1], pCxt->regs[A2], pCxt->regs[A3]);
    kprintf("t0= %08x\tt1= %08x\tt2= %08x\tt3= %08x\n",
        pCxt->regs[T0], pCxt->regs[T1], pCxt->regs[T2], pCxt->regs[T3]);
    kprintf("t4= %08x\tt5= %08x\tt6= %08x\tt7= %08x\n",
        pCxt->regs[T4], pCxt->regs[T5], pCxt->regs[T6], pCxt->regs[T7]);
    kprintf("t8= %08x\tt9= %08x\tk0= %08x\tk1= %08x\n",
        pCxt->regs[T8], pCxt->regs[T9], pCxt->regs[K0], pCxt->regs[K1]);
    kprintf("s0= %08x\ts1= %08x\ts2= %08x\ts3= %08x\n",
        pCxt->regs[S0], pCxt->regs[S1], pCxt->regs[S2], pCxt->regs[S3]);
    kprintf("s4= %08x\ts5= %08x\ts6= %08x\ts7= %08x\n",
        pCxt->regs[S4], pCxt->regs[S5], pCxt->regs[S6], pCxt->regs[S7]);
    kprintf("s8= %08x\tgp= %08x\tat= %08x\n",
        pCxt->regs[FP], pCxt->regs[GP], pCxt->regs[ATF]);

    kprintf("epc= %08x\tstatus= %08x\tbadaddress= %08x\n",
        pCxt->cp0_epc, pCxt->cp0_status, pCxt->cp0_badvaddr);

    read_cp0_register($13, uCause);
    kprintf("Cause = 0x%08x\t", uCause);
    read_cp0_register($30, uCause);
    kprintf("ErrEpc = 0x%08x\n", uCause);
}

void DumpEPC(InterruptContext *pCxt)
{
    if (pCxt->cp0_epc < 0x80000000) {
        kprintf("\nepc = 0x%08x\n", pCxt->cp0_epc);
    }
}

void DoBpHandle(InterruptContext *pContext)
{
    DebugPrint(CURRENT_DEBUG_LEVEL, kprintf("trap into DoBpHandle\n"));

    DebugPrint(CURRENT_DEBUG_LEVEL, DumpCxt(pContext));

    myChat(pContext);
}

void DoTlbDoubleFault(InterruptContext *pContext, Address badAddr)
{
    uint32_t    uEntryHi, uEntryLo0, uEntryLo1, uPageMask;
    int         nIndex;

    DebugPrint(0,
        kprintf("trap into DoTlbDoubleFault 0x%08x\n", badAddr));
    DebugPrint(0, DumpCxt(pContext));

    nIndex = FindTlb(badAddr);
    if (nIndex > 0) {
        uint32_t    uCurrentEntryLo;
        ReadTlb(nIndex, &uEntryHi, &uEntryLo0, &uEntryLo1, &uPageMask);
        if (badAddr & 0x1000) {
            uCurrentEntryLo = uEntryLo1;
        }
        else {
            uCurrentEntryLo = uEntryLo0;
        }

        if (0 != uCurrentEntryLo) {
            Cli();
            assert(!(uCurrentEntryLo & 0x2));
            kprintf(
                "EntryHi = 0x%08x, EntryLo0 = 0x%08x, EntryLo1 = 0x%08x\n",
                uEntryHi, uEntryLo0, uEntryLo1);
            kprintf("The address 0x%08x isn't valid\n", badAddr);

            myChat(pContext);
        }
    }

    if (badAddr >= K0BASE) {
        Cli();
        for (int i = 0; i < 48; i++) {
            ReadTlb(i, &uEntryHi, &uEntryLo0, &uEntryLo1, &uPageMask);
            kprintf(
                "%d EntryHi = 0x%08x, EntryLo0 = 0x%08x, uEntryLo1 = 0x%08x\n",
                i, uEntryHi, uEntryLo0, uEntryLo1);
        }

        DumpCxt(pContext);
        myChat(pContext);
    }

    FillTlb(badAddr, pContext);
}

void DoCacheError(InterruptContext *pContext)
{
    DebugPrint(CURRENT_DEBUG_LEVEL, kprintf("trap into DoCacheError\n"));

    DebugPrint(CURRENT_DEBUG_LEVEL, DumpCxt(pContext));

    myChat(pContext);
}

void DoGenericException(InterruptContext *pContext)
{
    DebugPrint(CURRENT_DEBUG_LEVEL, kprintf("trap into DoGenericException\n"));
    DebugPrint(CURRENT_DEBUG_LEVEL,
        kprintf("Casue = %d\n", (pContext->cp0_cause & 0xff) >> 2));

    DebugPrint(CURRENT_DEBUG_LEVEL, DumpCxt(pContext));

    myChat(pContext);
}

void DoFpuException(InterruptContext *pContext)
{
    DebugPrint(0, kprintf("trap into DoFpuException\n"));
    DebugPrint(0, DumpCxt(pContext));

#ifdef _NOFLOAT
    ASM("break 0;");
#else

    if (!DoDivZ(pContext)) {
        uint32_t    uFPUStatus;

#if defined(_GNUC)
        ASM("cfc1   %0, $31;":"=&r"(uFPUStatus));
#elif defined(_EVC)
        ASM(
            "cfc1   $12, $31;"
            "sw     $12, 0(%0);", &uFPUStatus);
#else
#error unknown compiler
#endif
        kprintf("uFPUStatus = 0x%08x\n", uFPUStatus);
        kprintf("the instruction is %08x\n", *(uint32_t *)pContext->cp0_epc);
        myChat(pContext);
    }
#endif  // _NOFLOAT
}

void DoCopException(InterruptContext *pContext)
{
    DebugPrint(0, kprintf("trap into DoCopException\n"));

    DebugPrint(0, DumpCxt(pContext));

    if (pContext->cp0_epc >= K0BASE && IS_IN_USER_MODE(pContext)) {
        kprintf("In the user mode, cannot change the status register.\n");
        Cli();
        DumpCxt(pContext);
        myChat(pContext);
    }

#ifdef _NOFLOAT
    kprintf("Cannot use coprocess 1\n");
    Cli();
    DumpCxt(pContext);
    myChat(pContext);
#else
    // You must enable cp1 first, because below function maybe must the cp1
    EnableCP1();

    GetCurrentThread()->m_hcontext.RestoreFPU(pContext);
#endif
}

void DoTLBModException(InterruptContext *pContext)
{
    DebugPrint(CURRENT_DEBUG_LEVEL, kprintf("trap into DoTLBModException\n"));

    DebugPrint(CURRENT_DEBUG_LEVEL, DumpCxt(pContext));

    myChat(pContext);
}

void DoAdeException(InterruptContext *pContext)
{
    unsigned long   pc;
    DebugPrint(0, kprintf("trap into DoAdeException\n"));
    DebugPrint(0, DumpCxt(pContext));

    AcknowledgeException();

    if (pContext->cp0_badvaddr >= K0BASE
        && IS_IN_USER_MODE(pContext)) {
        // TODO(mips): deal with -- kill current process
        Cli();
        kprintf("Access the privilege section\n");
        DumpCxt(pContext);
        myChat(pContext);
    }

    // retain the instruction that caused the exception
    pc = pContext->cp0_epc + ((pContext->cp0_cause & CAUSEF_BD) ? 4 : 0);

    if (!EmulateLoadStoreInsn(pContext, pContext->cp0_badvaddr, pc)) {
        ComputeReturnEpc(pContext);
    }
    else {
        Cli();
        kprintf("Cannot deal with the address load/store error\n");
        DumpCxt(pContext);
        myChat(pContext);
    }

    return;
}

void DoSysCallException(InterruptContext *pContext)
{
    DebugPrint(0, kprintf("trap into DoSysCallException\n"));

    DebugPrint(0, DumpCxt(pContext));

    AcknowledgeException();

    assert(pContext->cp0_status & 0x2);
    assert(0 == (pContext->cp0_status & 0x4));

    switch(pContext->regs[V0]) {
    // It's for test
    case 33: {
        kprintf("%s", pContext->regs[A0]);
        break;
    }
    case 34: {
        kprintf("%c", pContext->regs[A0]);
        break;
    }
    case 35: {
        kprintf("goto 0x%08x\t", pContext->regs[V1]);
        break;
    }

    // It's for xinhao
#ifdef _XINHAO
    // __InterlockedIncrement
    case 36: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = ++(*(Int32 *)pContext->regs[A0]);
        break;
    }

    // __InterlockedDecrement
    case 37: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = --(*(Int32 *)pContext->regs[A0]);
        break;
    }

    // __InterlockedExchangeAdd
    case 38: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = *(Int32 *)pContext->regs[A0];
        *(Int32 *)pContext->regs[A0] += pContext->regs[A1];
        break;
    }

    // __InterlockedExchange
    case 39: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = *(Int32 *)pContext->regs[A0];
        *(Int32 *)pContext->regs[A0] = pContext->regs[A1];
        break;
    }

    // __InterlockedExchangePointer
    case 40: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = *(Int32 *)pContext->regs[A0];
        *(Int32 *)pContext->regs[A0] = pContext->regs[A1];
        break;
    }

    // __InterlockedCompareExchange
    case 41: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = *(Int32 *)pContext->regs[A0];
        if (pContext->regs[V0] == pContext->regs[A2]) {
            *(Int32 *)pContext->regs[A0] = pContext->regs[A1];
        }
        break;
    }

    // __InterlockedCompareExchangePointer
    case 42: {
        assert(!(pContext->regs[A0] & 0x3));
        pContext->regs[V0] = *(Int32 *)pContext->regs[A0];
        if (pContext->regs[V0] == pContext->regs[A2]) {
            *(Int32 *)pContext->regs[A0] = pContext->regs[A1];
        }

        break;
    }
#endif

    default:
        Cli();
        kprintf("Unknown syscall, NO. = %d\n", pContext->regs[V0]);
        DumpCxt(pContext);
        myChat(pContext);
    }

    if (!DelaySlot(pContext)) {
        pContext->cp0_epc += 4;
    }
    else {
        Cli();
        kprintf("Syscall exceptoin cause in the delay slot\n");
        DumpCxt(pContext);
        myChat(pContext);
    }
}

#ifdef _XINHAO
void DoMachineCheckException(InterruptContext *pContext)
{
    DebugPrint(CURRENT_DEBUG_LEVEL,
        kprintf("trap into DoMachineCheckException\n"));

    DebugPrint(CURRENT_DEBUG_LEVEL, DumpCxt(pContext));

    ViewCurrentTlb();

    myChat(pContext);
}
#endif

void DoIrqDispatch(InterruptContext *pContext)
{
    irqs_t      uIrqs;

    DebugPrint(0, kprintf("trap into DoIrqDispatch\n"));

    DebugPrint(0, DumpCxt(pContext));

    uIrqs = BspGetIrqs();

    bool_t bOriginalPreemption = DzDisablePreemption();

#ifdef _p6032
    if (0 == uIrqs) { // spurious irq
        DzRestorePreemption(bOriginalPreemption);
        return;
    }
#endif // _p6032

    // it8172 or p6032
    if ((1 << 2) & uIrqs) {
        HandleInterrupt(2, pContext);
    }

    // Inter timer
    if ((1 << 7) & uIrqs) {
        HandleInterrupt(7, pContext);
    }

#ifdef _p6032
    // FW82371
    if ((1 << 3) & uIrqs) {
        HandleInterrupt(3, pContext);
    }

    assert(uIrqs & ((1 << 2) | (1 << 3) | (1 << 7)));
#else
    assert(uIrqs & ((1 << 2) | (1 << 7)));
#endif // _p6032

    DzRestorePreemption(bOriginalPreemption);

    if (0 < g_uLostTicks) {
        UpdateTime(pContext);
    }
}

EXTERN_C void MaybeSchedule()
{
    if (PreemptionEnabled() &&
        InterlockedExchange(&GetCurrentThread()->m_bNeedReschedule, FALSE)) {
        Schedule();
    }
}

EXTERN_C void AcknowledgeException()
{
    //
    // now, the all registers must be saved, now clear SR[EXL]
    // and  SR[ERL] and change the privilege to the kernel mode.
    //
    unsigned int uRes = 0;

    read_cp0_register($12, uRes);
    uRes = uRes & (~SR_EXL) & (~SR_ERL) & 0xffffffe7;
    write_cp0_register($12, uRes);
}
